package com.rs.game.player.content.grandExchange;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import com.google.common.collect.ImmutableSet;
import com.rs.cache.loaders.ItemDefinitions;
import com.rs.game.World;
import com.rs.game.player.Player;
import com.rs.utils.SerializableFilesManager;
import com.rs.utils.Utils;

public class GrandExchange {

	private static final Object LOCK = new Object();
	private static HashMap<Long, Offer> OFFERS;
	private static ArrayList<OfferHistory> OFFERS_TRACK;
	public static HashMap<Integer, Integer> PRICES;


	private static boolean edited;


	public static void init() {
		OFFERS = SerializableFilesManager.loadGEOffers();
		OFFERS_TRACK = SerializableFilesManager.loadGEHistory();
		PRICES = SerializableFilesManager.loadGEPrices();
	}

	public static void reset(boolean track, boolean price) {
		if(track)
			OFFERS_TRACK.clear();
		if(price)
			PRICES.clear();
		recalcPrices();
	}


	public static void recalcPrices() {
		ArrayList<OfferHistory> track = new ArrayList<OfferHistory>(OFFERS_TRACK);
		HashMap<Integer, BigInteger> averagePrice = new HashMap<Integer, BigInteger>();
		HashMap<Integer, BigInteger> averageQuantity = new HashMap<Integer, BigInteger>();
		for (OfferHistory o : track) {
			BigInteger price = averagePrice.get(o.getId());
			if (price != null) {
				BigInteger quantity = averageQuantity.get(o.getId());
				averagePrice.put(o.getId(), price.add(BigInteger.valueOf(o.getPrice())));
				averageQuantity.put(o.getId(), quantity.add(BigInteger.valueOf(o.getQuantity())));
			} else {
				averagePrice.put(o.getId(), BigInteger.valueOf(o.getPrice()));
				averageQuantity.put(o.getId(), BigInteger.valueOf(o.getQuantity()));
			}
		}

		for (int id : averagePrice.keySet()) {
			BigInteger price = averagePrice.get(id);
			BigInteger quantity = averageQuantity.get(id);

			long oldPrice = getPrice(id);
			long newPrice = price.divide(quantity).longValue();
			long min = (long) ((double) oldPrice * 0.95D) - 100;
			long max = (long) ((double) oldPrice * 1.05D) + 100;
			if (newPrice < min)
				newPrice = min;
			else if (newPrice > max)
				newPrice = max;

			if (newPrice < 1)
				newPrice = 1;
			else if (newPrice > Integer.MAX_VALUE)
				newPrice = Integer.MAX_VALUE;

			PRICES.put(id, (int) newPrice);
		}
		savePrices();
	}

	public static void savePrices() {
		SerializableFilesManager.saveGEPrices(PRICES);
	}

	public static final void save() {
		if (!edited)
			return;
		SerializableFilesManager.saveGEOffers(OFFERS);
		SerializableFilesManager.saveGEHistory(OFFERS_TRACK);
		edited = false;
	}

	public static void linkOffers(Player player) {
		boolean itemsWaiting = false;
		for (int slot = 0; slot < player.getGeManager().getOfferUIds().length; slot++) {
			Offer offer = getOffer(player, slot);
			if (offer == null)
				continue;
			offer.link(slot, player);
			offer.update();
			if (!itemsWaiting && offer.hasItemsWaiting()) {
				itemsWaiting = true;
				player.getPackets().sendGameMessage("You have items from the Grand Exchange waiting in your collection box.");
			}
		}
	}

	public static Offer getOffer(Player player, int slot) {
		synchronized (LOCK) {
			long uid = player.getGeManager().getOfferUIds()[slot];
			if (uid == 0)
				return null;
			Offer offer = OFFERS.get(uid);
			if (offer == null) {
				player.getGeManager().getOfferUIds()[slot] = 0; 
				return null;
			}
			return offer;
		}

	}

	private static final Set<Integer> NABS =  ImmutableSet.of(2361, 2363, 449, 451, 1513, 1747, 219, 269, 5304, 14479,
			3140, 14484, 11335, 6739, 24365, 11256, 15259, 19346, 19348, 19350, 19352, 19354, 19356, 19358, 19360
			,24352, 11284, 1615, 18830, 18832, 534, 536, 17676, 14474, 14476, 14478, 6, 8, 10, 12, 11967, 1053, 1055, 1057, 1038, 1040
			,1042, 1044, 1046, 1048, 962, 1050, 981, 1959, 1961, 6585, 19335, 11696, 11704, 11724, 11726, 11728, 15270, 15272
			,11694, 11702, 11718, 11720, 11722, 12670, 21777, 11700, 11708, 11716, 989, 985, 987, 21787, 21793, 21790, 21773, 2577, 10550, 2581
			, 13746, 13748, 13750, 13752, 13754, 13746, 13738, 13740, 13742, 13744, 7158, 13858, 13861, 13864, 13867, 13932, 13935
			, 13938, 13941, 13887, 13893, 13899, 13905, 13911, 13917, 13923, 13929, 13884, 13890, 13896, 13902, 13908, 13914, 13920, 13926
			, 13870, 13873, 13876, 13879, 13880, 13881, 13882, 13883, 13944, 13947, 13950, 13953, 13954, 13955, 13956, 13957, 10330
			, 10332, 10334, 10336, 10338, 10340, 10342, 10344, 10346, 10348, 10350, 10352, 19308, 19311, 19314, 19317, 19320, 11858, 10860, 11862
			, 19580, 6, 8, 10, 12, 24338, 24340, 24342, 24344, 24346, 24372, 24374, 11846, 11848, 11850, 11852, 11854, 11856, 11286, 6737
			, 6733, 6731);

	
	public static void sendOffer(Player player, int slot, int itemId, int amount, int price, boolean buy) {
		synchronized (LOCK) {
			Offer offer = new Offer(itemId, amount, price, buy);
			player.getGeManager().getOfferUIds()[slot] = createOffer(offer);
			offer.link(slot, player);
			if (NABS.contains(itemId) || ItemDefinitions.getItemDefinitions(itemId).getName().contains("armour set"))
			{
				findBuyerSeller(offer);
				World.sendWorldMessage("<col="+player.getYellColor()+">[GE] "+player.getDisplayName()+" is "+(buy ? "buying:" : "selling:")+" "+(offer.getAmount() >= 2 ? ""+offer.getAmount()+""  : "one")+" "
						+ ""+ItemDefinitions.getItemDefinitions(itemId).getName().toLowerCase()+""+(offer.getAmount() >= 2 ? "s" : "")+" for "+(price * amount)+" coins!", false);
			} 
			else if (!NABS.contains(itemId) && price == GrandExchange.getPrice(itemId)) 
				findAutoBuyerSeller(offer);
			else 
			{
				findBuyerSeller(offer);
				World.sendWorldMessage("<col="+player.getYellColor()+">[GE] "+player.getDisplayName()+" is "+(buy ? "buying:" : "selling:")+" "+(offer.getAmount() >= 2 ? ""+offer.getAmount()+""  : "one")+" "
						+ ""+ItemDefinitions.getItemDefinitions(itemId).getName().toLowerCase()+""+(offer.getAmount() >= 2 ? "s" : "")+" for "+(price * amount)+" coins!", false);

			}
		}
	}

	public static void abortOffer(Player player, int slot) {
		synchronized (LOCK) {
			Offer offer = getOffer(player, slot);
			if (offer == null)
				return;
			edited = true;
			if (offer.cancel() && offer.forceRemove())
				deleteOffer(player, slot); 
		}
	}

	public static void collectItems(Player player, int slot, int invSlot, int option) {
		synchronized (LOCK) {
			Offer offer = getOffer(player, slot);
			if (offer == null)
				return;
			edited = true;
			if (offer.collectItems(invSlot, option) && offer.forceRemove()) {
				deleteOffer(player, slot); 
				if (offer.getTotalAmmountSoFar() != 0) {
					OfferHistory o = new OfferHistory(offer.getId(), offer.getTotalAmmountSoFar(), offer.getTotalPriceSoFar(), offer.isBuying());
					OFFERS_TRACK.add(o);
					player.getGeManager().addOfferHistory(o);
				}
			}
		}
	}

	private static void deleteOffer(Player player, int slot) {
		player.getGeManager().cancelOffer(); 
		OFFERS.remove(player.getGeManager().getOfferUIds()[slot]);
		player.getGeManager().getOfferUIds()[slot] = 0;
	}

	private static void findBuyerSeller(Offer offer) {
		while (!offer.isCompleted()) {
			Offer bestOffer = null;
			for (Offer o : OFFERS.values()) {
				if (o.isBuying() == offer.isBuying() || o.getId() != offer.getId() || o.isCompleted() || (offer.isBuying() && o.getPrice() > offer.getPrice()) || (!offer.isBuying() && o.getPrice() < offer.getPrice()) || offer.isOfferTooHigh(o))
					continue;
				if (bestOffer == null || (offer.isBuying() && o.getPrice() < bestOffer.getPrice()) || (!offer.isBuying() && o.getPrice() > bestOffer.getPrice()))
					bestOffer = o;
			}
			if (bestOffer == null)
				break;
			offer.updateOffer(bestOffer);
		}
		offer.update();
	}

	private static void findAutoBuyerSeller(Offer offer) {
		offer.updateAutoOffer(offer);
		//offer.update();

	}

	private static long createOffer(Offer offer) {
		edited = true;
		long uid = getUId();
		OFFERS.put(uid, offer);
		return uid;
	}

	private static long getUId() {
		while (true) {
			long uid = Utils.RANDOM.nextLong();
			if (OFFERS.containsKey(uid))
				continue;
			return uid;
		}
	}

	public static int getPrice(int itemId) {
		ItemDefinitions defs = ItemDefinitions.getItemDefinitions(itemId);
		if (defs.isNoted())
			itemId = defs.getCertId();
		else if (defs.isLended())
			itemId = defs.getLendId();
		Integer price = PRICES.get(itemId);
		return price != null && price != 0 ? price : defs.getValue();
	}

	public static void unlinkOffers(Player player) {
		for (int slot = 0; slot < player.getGeManager().getOfferUIds().length; slot++) {
			Offer offer = getOffer(player, slot);
			if (offer == null)
				continue;
			offer.unlink();
		}
	}

	public static List<OfferHistory> getHistory() {
		return OFFERS_TRACK;
	}
}
